﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using UnityEngine;
using UnityEngine.UI;

public static class UICommonMethod
{
    #region GameObject
    /// <summary>
    /// 销毁给定Transform下所有子节点
    /// </summary>
    public static void DestroyAllChildren(Transform tran)
    {
        var childCount = tran.childCount;

        for (int i = 0; i < childCount; i++)
        {
            GameObject.Destroy(tran.GetChild(i).gameObject);
        }
    }
    #endregion

    #region ScrollView
    /// <summary>
    /// 根据Content内容的数量和尺寸，自动调整滚动视图的Viewport大小。
    /// 目前只支持垂直滚动视图。
    /// </summary>
    /// <param name="scrollRect">滚动视图</param>
    /// <param name="maxDisplayItems">Viewport能显示的最大Item数量。
    /// 注1：本函数会根据每个Item的实际尺寸来计算Viewport的大小，所以自动调整后的Viewport不会令Item显示发生截断。
    /// 注2：为确保本函数所计算出的尺寸是正确的，请确保所修改的ScrollRect的Content是使用了VerticalLayoutGroup，
    ///      且设置正确，以及垂直方向没有处于ContentSizeFitter的控制下。
    /// </param>
    public static float AutoAdjustScrollViewportSize(ScrollRect scrollRect, int maxDisplayItems)
    {
        // 前置检查：ScrollRect，Viewport，Content缺一不可
        if (scrollRect == null) { return -1; }

        var viewport = scrollRect.viewport;
        if (viewport == null) { return -1; }

        var content = scrollRect.content;
        if (content == null) { return -1; }

        if (maxDisplayItems < 0)
        {
            maxDisplayItems = 0;
        }

        if (viewport != null && content != null)
        {
            float itemSpacing = 0;
            float topPadding = 0;
            float bottomPadding = 0;

            var verticalLayoutGroup = content.GetComponent<VerticalLayoutGroup>();

            if (verticalLayoutGroup != null)
            {
                itemSpacing = verticalLayoutGroup.spacing;
                topPadding = verticalLayoutGroup.padding.top;
                bottomPadding = verticalLayoutGroup.padding.bottom;
            }

            float adjustedViewportHeight = 0.0f;
            {
                bool itemAdded = false;
                float lastItemHeight = 0.0f;

                var itemCount = content.childCount;

                for (int i = 0; i < itemCount && i < maxDisplayItems; i++)
                {
                    var itemTran = content.GetChild(i);
                    var item = itemTran.gameObject;

                    if (item != null && item.activeSelf)
                    {
                        RectTransform itemRectTran = item.GetComponent<RectTransform>();

                        if (itemRectTran != null)
                        {
                            itemAdded = true;
                            adjustedViewportHeight += itemRectTran.sizeDelta.y;
                            adjustedViewportHeight += itemSpacing;

                            lastItemHeight = itemRectTran.sizeDelta.y;
                        }
                    }
                }

                if (itemAdded && itemCount > maxDisplayItems)
                {
                    adjustedViewportHeight -= itemSpacing;
                    adjustedViewportHeight -= lastItemHeight * 0.5f;
                }
            }

            {
                adjustedViewportHeight += topPadding;
                adjustedViewportHeight += bottomPadding;
            }

            // 为滚动视图的viewport设置计算后的高度
            {
                var sizeDelta = viewport.sizeDelta;
                sizeDelta.y = adjustedViewportHeight;
                viewport.sizeDelta = sizeDelta;
            }
            return adjustedViewportHeight;
        }
        return -1;
    }

    /// <summary>
    /// 在给定的scrollRect节点下，查找给定的targetItem，并尝试滚动到该targetItem。
    /// 
    /// 注：1.目前仅支持纵向滚动。
    ///     2.scrollRect的content的RectTransform，纵向不能使用拉伸对齐。
    ///     3.本函数会尝试将targetItem滚动到scrollRect的viewport的中心，但不会使content超过scrollRect的最大最小滚动位置。
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="targetItem"></param>
    public static void ScrollToItem(ScrollRect scrollRect, GameObject targetItem)
    {
        // 前置检查：ScrollRect，Viewport，Content缺一不可
        if (scrollRect == null) { return; }

        var viewport = scrollRect.viewport;
        if (viewport == null) { return; }

        var content = scrollRect.content;
        if (content == null) { return; }

        // 要滚动到的item也不能少
        if (targetItem == null) { return; }

        // 并且要求该item的父节点必须是content节点
        if (targetItem.transform.parent != content) { return; }

        // 确认了所有参数后，就可以进行滚动了
        ScrollToItemInternal(scrollRect, targetItem);
    }

    public static void ScrollToItem(ScrollRect scrollRect, Func<GameObject, bool> findItemFunc)
    {
        // 前置检查：ScrollRect，Viewport，Content缺一不可
        if (scrollRect == null) { return; }

        var viewport = scrollRect.viewport;
        if (viewport == null) { return; }

        var content = scrollRect.content;
        if (content == null) { return; }

        // 查找Item的回调当然也不能少
        if (findItemFunc == null) { return; }

        // 滚动逻辑
        {
            GameObject item = null;

            // 通过传入的回调函数查找Item，并获取Item的尺寸和位置
            {
                var contentTran = content.transform;
                var contentTranChildCount = contentTran.childCount;

                for (int i = 0; i < contentTranChildCount; i++)
                {
                    var tmpItem = contentTran.GetChild(i);

                    if (findItemFunc(tmpItem.gameObject))
                    {
                        item = tmpItem.gameObject;
                        break;
                    }
                }
            }

            // 如果找到了Item，就进行滚动
            if (item != null)
            {
                ScrollToItemInternal(scrollRect, item);
            }
        }
    }

    private static void ScrollToItemInternal(ScrollRect scrollRect, GameObject item)
    {
        // 获取滚动目标Item的尺寸以及在Content中的位置，并计算normallizedPosition
        var viewport = scrollRect.viewport;
        var content = scrollRect.content;
        var layoutGroup = content.GetComponent<VerticalLayoutGroup>();

        if (layoutGroup == null) { return; }

        //float itemHeight;
        float itemY;

        var itemRectTran = item.GetComponent<RectTransform>();

        if (itemRectTran != null)
        {
            //itemHeight = itemRectTran.sizeDelta.y;
            itemY = itemRectTran.anchoredPosition.y;
        }
        else
        {
            //itemHeight = 0.0f;
            itemY = item.transform.localPosition.y;
        }

        var contentHeight = content.sizeDelta.y;
        var contentCenterHeight = contentHeight * 0.5f;

        //var viewportHeight = viewport.sizeDelta.y;
        var viewportCenterHeight = viewport.sizeDelta.y * 0.5f;

        var minScrollVal = 0.0f;
        var maxScrollVal = content.sizeDelta.y - viewport.sizeDelta.y;

        if(maxScrollVal <= 0)
        {
            return;
        }

        float normPos;

        if (!Mathf.Approximately(maxScrollVal, 0.0f))
        {
            float contentBottomY = 0.0f;

            switch (layoutGroup.childAlignment)
            {
                case TextAnchor.LowerLeft:
                case TextAnchor.LowerCenter:
                case TextAnchor.LowerRight:
                    contentBottomY = 0.0f;
                    break;

                case TextAnchor.MiddleLeft:
                case TextAnchor.MiddleCenter:
                case TextAnchor.MiddleRight:
                    contentBottomY = -contentCenterHeight;
                    break;

                case TextAnchor.UpperLeft:
                case TextAnchor.UpperCenter:
                case TextAnchor.UpperRight:
                    contentBottomY = -contentHeight;
                    break;
            }

            float minScrollY = contentBottomY + viewportCenterHeight;
            var preferScrollVal = itemY - minScrollY;
            var finalScrollY = Mathf.Clamp(preferScrollVal, minScrollVal, maxScrollVal);
            normPos = finalScrollY / maxScrollVal;
        }
        else
        {
            normPos = 1.0f;
        }

        // 计算出normalizedPosition后，就可以开始滚动了
        ScrollToNormalizedPosition(scrollRect, normPos);
    }

    private static List<UGUITweenValue> __scrollToNormalizedPosition_tweenerListReuse = new List<UGUITweenValue>();

    /// <summary>
    /// 滚动到
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="normPos"></param>
    public static void ScrollToNormalizedPosition(ScrollRect scrollRect, float normPos, float duration = 0.3f, int tweenerGroup = 999)
    {
        if (scrollRect == null)
        {
            return;
        }

        var currNormPos = scrollRect.verticalNormalizedPosition;
        var destNormPos = normPos;

        UGUITweenValue tweener = null;

        {
            __scrollToNormalizedPosition_tweenerListReuse.Clear();
            scrollRect.GetComponents<UGUITweenValue>(__scrollToNormalizedPosition_tweenerListReuse);

            for (int i = 0; i < __scrollToNormalizedPosition_tweenerListReuse.Count; i++)
            {
                var tmpTweener = __scrollToNormalizedPosition_tweenerListReuse[i];

                if (tmpTweener.tweenGroup == tweenerGroup)
                {
                    tweener = tmpTweener;
                    break;
                }
            }
            __scrollToNormalizedPosition_tweenerListReuse.Clear();
        }

        if (tweener == null)
        {
            tweener = scrollRect.gameObject.AddComponent<UGUITweenValue>();
            tweener.onUpdate += OnUpdateTweenScrollViewValue;
            tweener.tweenGroup = tweenerGroup;
        }

        tweener.from = currNormPos;
        tweener.to = destNormPos;
        tweener.duration = duration;
        tweener.args = scrollRect;
        tweener.method = UGUITweener.Method.EaseOut;
        tweener.ResetToBeginning();
        tweener.PlayForward();
    }

    private static void OnUpdateTweenScrollViewValue(UGUITweenValue sender, float v)
    {
        var scrollRect = (ScrollRect)sender.args;
        scrollRect.verticalNormalizedPosition = v;
    }
    #endregion

    #region Text
    /// <summary>
    /// 保持行尾的特定字符显示在同一行。
    /// 注：这个函数的功能目前非常有限，不要对它提供的功能做过多的期待。
    /// </summary>
    /// <param name="text">要进行对齐处理的Text组件</param>
    /// <param name="startChars">要匹配的起始字符集合</param>
    /// <param name="endChars">要匹配的结束字符集合</param>
    /// <returns>是否变更了传入Text的文字内容。注：有可能变更之后的文字内容显示出来仍然不在同一行。</returns>
    public static bool KeepTrailingTextInSameLine(Text text, char[] startChars, char[] endChars)
    {
        // 先进行一系列判空
        if (text == null || startChars == null || endChars == null)
        {
            return false;
        }

        var textStr = text.text;

        if (string.IsNullOrEmpty(textStr))
        {
            return false;
        }

        var textGen = text.cachedTextGenerator;

        if (textGen == null)
        {
            return false;
        }

        var lines = textGen.lines;

        // 空行或只有一行的话，就不进行处理了。
        if (lines == null || lines.Count <= 1)
        {
            return false;
        }

        // 然后看起始字串和结束字串是否在传入的text的文字中存在。
        // 如果任何一个不存在就不进行处理。
        var startCharIdx = textStr.LastIndexOfAny(startChars);
        var endCharIdx = textStr.LastIndexOfAny(endChars);

        if (startCharIdx == -1 || endCharIdx == -1 || startCharIdx >= endCharIdx)
        {
            return false;
        }

        // 并查出起始字符和结束字符所在的行号。
        var startCharLineIdx = -1;
        var endCharLineIdx = -1;

        for (int i = 0; i < lines.Count; i++)
        {
            var tmpLineCharIdx = lines[i].startCharIdx;

            if (tmpLineCharIdx <= startCharIdx)
            {
                startCharLineIdx = tmpLineCharIdx;
            }
        }

        for (int i = 0; i < lines.Count; i++)
        {
            var tmpLineCharIdx = lines[i].startCharIdx;

            if (tmpLineCharIdx <= endCharIdx)
            {
                endCharLineIdx = tmpLineCharIdx;
            }
        }

        // 并确保获得了有效的行号。并且，如果起始字符和结束字符原本就在同一行，也不进行任何处理。
        if (startCharLineIdx == -1 || endCharLineIdx == -1 || startCharLineIdx == endCharLineIdx)
        {
            return false;
        }

        // 执行到这里就可以进行保持同一行的处理了。
        // 处理方法：起始字符前插入换行符，并删除起始字符到结束字符之间的所有换行符。
        // 如果这样都不能令起始结束字符在同一行，则说明起始结束字符之间的字串长度超过了Text控件的行宽，
        // 以至于不改变字体大小的情况下任何方法都不能令起始结束字符在同一行。
        var originalText = text.text;

        var sb = new StringBuilder();

        if (startCharIdx > 0)
        {
            sb.Append(originalText, 0, startCharIdx);
            sb.Append('\n');
        }

        var tmpStr = originalText.Substring(startCharIdx, endCharIdx - startCharIdx + 1).Replace("\n", "");
        sb.Append(tmpStr);

        if (endCharIdx < originalText.Length - 1)
        {
            sb.Append(originalText, endCharIdx + 1, (originalText.Length - 1) - (endCharIdx + 1) + 1);
        }

        text.text = sb.ToString();

        return true;
    }
    #endregion
}